<?php
/**
 * App Store 对外接口
 * 
 * @author wanggang <wanggang@snda.com>
 * @version 1.2
 * 
 * 2010-04-13
 */

require_once(dirname(__FILE__).'/config.php');
require_once(dirname(__FILE__).'/../../statistics/Statistic.inc.php');

/**
 * App Store 数据接口
 */
class SdoAppStore
{
    const APP_CACHE_TIME  = 3600;     //应用信息的缓存时间
    
    const USER_APP_CACHE_TIME = 600; //用户玩过（授权过）的应用列表缓存时间

    const APP_USER_CACHE_TIME = 600; //应用的最近使用用户，缓存时间

    const ALL_APPS_CACHE_TIME = 3600;
    
    const APP_FRIEND_COUNT_CACHE_TIME = 7200; //在玩一个应用的好友数缓存时间
    
    const MEMCACHE_SET_FLAGS  = 0;   //Memcache数据压缩开关

    const MEMCACHE_CLUSTER_ID = 1;  //App Store Model使用，保证MEMCAHCE缓存共用
    
    const CACHE_CLUSTER_SHARE = 'share';
    
    const CACHE_CLUSTER_APPINFO = 'appinfo';
        
    const APPINFO_MEMCACHE_CLUSTER_ID = 3;  //App Info 缓存
    
    /**
     * 应用状态
     */
    const APP_STATUS_DEVELOP = 1;                // 开发中
    const APP_STATUS_DEVELOP_PRIVATE = 2;        // 开发中私有
    const APP_STATUS_EVALUATE_OK = 3;            // 测评
    const APP_STATUS_CONFIG_OK = 4;              // 运营配置
    const APP_STATUS_AUDIT = 5;                  // 审核中
    const APP_STATUS_BETA = 6;                   // 内测中
    const APP_STATUS_RUN = 7;                    // 运营中
    const APP_STATUS_DEVELOP_STOP = 8;           // 开发者暂停
    const APP_STATUS_ADMIN_STOP = 9;             // 管理员暂停
    const APP_STATUS_DEVELOP_MAINT = 10;         // 开发者维护
    const APP_STATUS_ADMIN_MAINT = 11;           // 管理员维护
    const APP_STATUS_TEMP_CLOSE = 12;            // 临时查封
    const APP_STATUS_WAIT_FOR_UNCLOSE = 13;      // 等待解封
    const APP_STATUS_CLOSE = 14;                 // 永久查封
    const APP_STATUS_DELETE = 15;                // 删除
    
    /*
     * 应用类型
     */
    const APP_TYPE_WEB = 1;
    const APP_TYPE_SYSTEM = 16;
    
    /*
     * 开发类型
     */
    const CANVAS_TYPE_IFRAME = 1;
    const CANVAS_TYPE_SDML = 2;
    const CANVAS_TYPE_PAGE = 4;  //在新窗口打开
    
    /*
     * 应用宽度类型
     */
    const CANVAS_WIDTH_TYPE_STANDARD = 1;        // 标准宽度
    const CANVAS_WIDTH_TYPE_FULL = 2;            // 全屏
    
    public static $useCache = TRUE;  //缓存开关
    
    public static $enableLog = FALSE;   

    //缓存
    protected static $_appCache = array();
    
    protected static $_errors = array();
    
    protected static $_logs = array();
    
    protected static $_dbInstances = array();
    
    protected static $_memcaches = array();
    
    protected static $_appFields = array(
        'id', # int unsigned not null auto_increment primary key comment '应用ID',
        #creator_id bigint unsigned not null comment '创建者ID',
        #company_id int unsigned not null comment '开发公司ID',
        #category_id int unsigned not null comment '应用分类ID',
        'name', #varchar(32) not null comment '应用名称',
        'intro', #text not null comment '应用简介',
        'icon_small', # varchar(255) not null comment '应用小图标',
        'icon_middle', # varchar(255) not null comment '应用中图标',
        'icon_big', #varchar(255) not null comment '应用大图标',
        #'api_key', #char(32) not null comment 'API Key',
        #'secret_key', # char(32) not null comment 'Secret Key',
        #'system_secret_key', # char(32) not null comment 'System Secret Key',
        #authorization_callback varchar(255) not null comment '当用户授权你的应用后，开放平台会回调这个地址通知应用',
        #remove_callback varchar(255) not null comment '当用户删除你的应用后，开放平台会回调这个地址通知应用',
        'canvas_page_url', # varchar(255) not null comment '应用在开放平台展示的基本地址',
        'canvas_callback_url', # varchar(255) not null comment '应用服务的实际地址',
        #wap_canvas_callback_url varchar(255) not null comment '应用服务的Wap实际地址',
        #bookmark_url varchar(255) not null comment '当用户点击收藏应用后的链接地址',
        #authorization_redirect varchar(255) not null comment '用户第一次授权应用后将跳转到这个地址',
        'canvas_type', # tinyint unsigned not null comment '开发类型',
        'app_width', # tinyint unsigned not null comment '应用宽度类型',
        'app_height', # int unsigned not null comment '应用高度',
        'app_type', # tinyint unsigned not null comment '应用类型',
        'status', # tinyint unsigned not null comment '应用状态',
        #service_phone varchar(32) not null comment '客服电话',
        #service_email varchar(64) not null comment '客服Email',
        #pay_type tinyint unsigned not null comment '支付类型',
        #pay_status tinyint unsigned not null comment '支付状态',
        'total_user_count', # int unsigned not null comment '总用户数',
        'private_install_mode', # tinyint unsigned not null comment '私有安装模式',
        'last_active_user_count', # int unsigned not null comment '昨日活跃用户数',
        'last_day_pv', # int unsigned not null comment '昨日PV',
        #create_time datetime not null comment '创建时间',
        #audit_time datetime not null comment '提交审核时间',
        #publish_time datetime not null comment '发布时间',
        #operation_level tinyint not null default 1 comment '运营等级 1：一般 2：比较高 3：高',
        #verify_login tinyint not null default 1 comment '是否需要验证用户登入 1:验证 0:否',
        #role_id int not null default 1 comment '应用角色id',
    );

    //******************************
    // 公开接口 START
    //******************************
    
    /**
     * 获取单个应用信息
     * 
     * @param integer $appId 应用id
     * @return array 不存在返回NULL
     */
    public static function getAppInfo($appId)
    {
        return self::_getApps($appId);
    }
    
    /**
     * 批量获取应用信息
     * 
     * @param array $appIds 应用id数组
     * @return array 应用的数组，应用id作为数组的key
     * @example
     *      SdoAppStore::getBatchAppInfo(array(101, 132, 111));
     *      
     *      返回
     *      array(
     *          101 => array('id' => '101', 'name' => '...', ...),
     *          132 => array('id' => '132', 'name' => '...', ...),
     *          ...
     *      )
     */
    public static function getBatchAppInfo($appIds)
    {
        return self::_getApps($appIds);   
    }
    
    /**
     * 获取一个用户玩过（授权过）的应用
     * 
     * @param integer $uid   用户id
     * @param integer $count 返回个数，默认0，为0表示返回所有
     * @param boolean $innerGetSort 仅内部调用使用，用于获取排序规则
     * @return array  返回应用信息的数组，没有应用返回空数组
     *      array(
     *          array('id' => '101', 'name' => '...', ...),
     *          array('id' => '101', 'name' => '...', ...),
     *      )
     */
    public static function getUserApps($uid, $count = 0, $innerGetSort = FALSE)
    {
        $sort = 'position ASC, create_time ASC';
        
        if ($innerGetSort) {
            return $sort;
        }

        return self::_getUserApps($uid, $sort, $count);
    }

    /**
     * 批量获取用户最近玩过的应用
     *
     * @param array $uids 用户的uid数组 
     * @param integer $count 每个用户最近玩过应用返回的最大个数
     * @return array 以uid为键值的数组 array(uid => array( app_id => app_info_array, ...), ...)
     */
    public static function getUsersRecentlyPlayedApps($uids, $count = 0)
    {
        $ret = array();
        foreach ($uids as $uid) {
            $ret[$uid] = self::getUserRecentlyPlayedApps($uid, $count);
        }
        return $ret;
    }

    /**
     * 获取一个用户最近玩过的应用
     *
     * @param integer $uid 用户id
     * @param integer $count 默认0, 返回个数， 0表示返回所有
     * @param boolean $innerGetSort 仅内部调用使用，用于获取排序规则
     * @return array
     */
    public static function getUserRecentlyPlayedApps($uid, $count = 0, $innerGetSort = FALSE)
    {
        $sort = 'last_access_time DESC';
        
        if ($innerGetSort) {
            return $sort;
        }

        return self::_getUserApps($uid, $sort, $count);
    }
    

    /**
     * 批量获取多个应用的最近使用用户
     *
     * @param array $appIds
     * @param integer $maxcount
     * @return array array( appId => array(uid, ...), ...)
     */
    public static function getAppsUsers($appIds, $maxCount = 1000)
    {
        $ret = array();
        foreach ($appIds as $appId) {
            $ret[$appId] = self::getAppUsers($appId, $maxCount);
        }
        return $ret;
    }

    /**
     * 获取一个应用的最近使用用户
     * 
     * @param integer $appId
     * @param integer $maxCount 需要返回的数目，默认值1000 
     * @return array uid数组，没有数据返回空数组
     */
    public static function getAppUsers($appId, $maxCount = 1000)
    {
        $cacheKey = "app_{$appId}_user_{$maxCount}";
        
        $uids = NULL;
        if (self::$useCache) {
            $uids = self::_getCache($cacheKey, self::MEMCACHE_SET_FLAGS);
        }
        
        if (!empty($uids)) {
            self::_log("getAppUsers:get from cache");
            return $uids;
        }
        
        $sql = 'SELECT uid FROM app_user WHERE app_id=' . intval($appId)
             . ' ORDER BY last_access_time DESC limit 0,' . intval($maxCount);
        
        $db  = self::_getAppUserDb($appId);
        
        $uids = array();
        
        if ($db) {
            $res = @mysql_query($sql, $db);
            
            if ($res) {
                while (($row = @mysql_fetch_assoc($res)) != FALSE) {
                    $uids[] = $row['uid'];        
                }
                @mysql_free_result($res);
                
                self::_setCache(
                    $cacheKey, $uids, 
                    self::MEMCACHE_SET_FLAGS,
                    self::APP_USER_CACHE_TIME
                );
            } else {
                self::_error("getAppUsers:query error\n$sql\n".mysql_error($db));
            }
            
            #@mysql_close($db);
        } else {
            self::_error("getAppUsers:no db link");
        }
        
        return $uids;
    }
    
    /**
     * 获取在玩某一应用的好友列表(uid数组)
     * 
     * @param int $uid
     * @param int $appId
     * @return array uid数组
     */
    public static function getUserFriendsInApp($uid, $appId)
    {
        include_once(dirname(__FILE__).'/../../uc/models/relation/UCModRelation.php');
        
        $uid = intval($uid);
        $appId = intval($appId);

        $cacheKey = self::getUserFriendsInAppCacheKey($uid, $appId);
        if (self::$useCache) {
            $uids = self::_getCache($cacheKey, self::MEMCACHE_SET_FLAGS);
            if ($uids !== FALSE) {
                self::_log("getUserFriendsInApp:get from cache");
                return $uids;
            }            
        }
        $friendUids = array();

        $relation = new UCModRelation($uid);
        #$relations = $relation->getFollowsDistinct(0, 2000);
        $relations = $relation->getBiRelation(0, 2000);
        if ($relations && $relations['entry']) {
            foreach ($relations['entry'] as $item) {
                $friendUids[] = $item['fsdid'];
            }
        }
        #var_dump($friendUids);
        $uids = array();
        if (empty($friendUids)) {
            //no friends
            self::_log('getUserFriendsInApp: no friends');
        } else {
            $sql = "SELECT uid FROM app_user WHERE app_id={$appId} AND uid in ("
                 . implode(',', $friendUids) . ')';

            $db  = self::_getAppUserDb($appId);
            if ($db) {
                $res = @mysql_query($sql, $db);
                if ($res) {
                    while (($row = @mysql_fetch_array($res)) != FALSE) {
                        $uids[] = $row['uid'];
                    }
                    @mysql_free_result($res);
                } else {
                    self::_error("getUserFriendsInApp:query error\nsql:{$sql}\n".mysql_error($db));
                }
                #@mysql_close($db);
            } else {
                self::_error("getUserFriendsInApp:no db link");
            }
        }

        if (self::$useCache) {
            self::_setCache(
                $cacheKey, $uids, 
                self::MEMCACHE_SET_FLAGS,
                self::APP_FRIEND_COUNT_CACHE_TIME
            );
            
            //cache count
            $friendCount = count($uids);
            $countCacheKey = self::getUserFriendCountInAppCacheKey($uid, $appId);
            self::_setCache(
                $countCacheKey, $friendCount,
                self::MEMCACHE_SET_FLAGS,
                self::APP_FRIEND_COUNT_CACHE_TIME
            );
        }

        return $uids;
    }
    
    /**
     * 返回玩一个应用的好友数
     *
     * @param integer $uid
     * @param integer $appId
     * @return integer
     */ 
    public static function getUserFriendCountInApp($uid, $appId)
    {
        include_once(dirname(__FILE__).'/../../uc/models/relation/UCModRelation.php');
        
        $uid = intval($uid);
        $appId = intval($appId);

        $cacheKey = self::getUserFriendCountInAppCacheKey($uid, $appId);

        if (self::$useCache) {
            $count = self::_getCache($cacheKey, self::MEMCACHE_SET_FLAGS);
            if ($count !== FALSE) {
                self::_log("getUserFriendCountInApp:get from cache");
                return intval($count);
            }            
        }

        $friendUids = array();

        $relation = new UCModRelation($uid);
        #$relations = $relation->getFollowsDistinct(0, 2000);
        $relations = $relation->getBiRelation(0, 2000);
        if ($relations && $relations['entry']) {
            foreach ($relations['entry'] as $item) {
                $friendUids[] = $item['fsdid'];
            }
        }
        #var_dump($friendUids);
        if (empty($friendUids)) {
            $count = 0;
        } else {
            $sql = "SELECT COUNT(*) FROM app_user WHERE app_id={$appId} AND uid in ("
                 . implode(',', $friendUids) . ')';
            $db  = self::_getAppUserDb($appId);
            if ($db) {
                $res = @mysql_query($sql, $db);
                if ($res) {
                    $row = @mysql_fetch_array($res);
                    if ($row !== FALSE) {
                        $count = $row[0];
                    }
                    @mysql_free_result($res);
                } else {
                    self::_error("getUserFriendCountInApp:query error\nsql:{$sql}\n".mysql_error($db));
                }
                #@mysql_close($db);
            } else {
                self::_error("getUserFriendCountInApp:no db link");
            }
        }

        if (self::$useCache) {
            self::_setCache(
                $cacheKey, $count, 
                self::MEMCACHE_SET_FLAGS,
                self::APP_FRIEND_COUNT_CACHE_TIME
            );
        }

        return intval($count);
    }

    public static function getHotApps($count)
    {
        $cacheKey = "hot_apps_{$count}";

        if (self::$useCache) {
            $appids = self::_getCache($cacheKey, self::MEMCACHE_SET_FLAGS);

            if ($appids) {
                self::_log("get hot apps $count from cache!");
                return self::getBatchAppInfo($appids);
            }
        }

        $db = self::_getAppDb();
        if (!$db) {
            self::_error("no app db link");
            return array();
        }
        
        $sql = 'SELECT id FROM application WHERE `status`=' . self::APP_STATUS_RUN
             . ' AND app_type != ' . self::APP_TYPE_SYSTEM
             . ' ORDER BY total_user_count DESC LIMIT 0, ' . intval($count);
        
        $res = @mysql_query($sql, $db);

        $appids = array();
        if ($res) {
            while (($row = @mysql_fetch_assoc($res)) !== FALSE) {
                $appids[] = $row['id'];
            }
        }
        
        if (self::$useCache) {
            self::_setCache($cacheKey, $appids, self::MEMCACHE_SET_FLAGS, 300);
        }

        $apps = self::getBatchAppInfo($appids);
        $ret = array();
        foreach ($appids as $appid) {
            if (isset($apps[$appid])) {
                $ret[] = $apps[$appid];
            }
        }

        return $ret;
    }

    /**
     * 返回所有应用
     *      $offset及$count参数作废，请匆使用
     * @return array
     */
    public static function getAllApps($offset = 0, $count = 200)
    {
        $cacheKey = self::_getAllAppCacheKey();
        
        if (self::$useCache) {
            $apps = self::_getCache($cacheKey, self::MEMCACHE_SET_FLAGS);
            
            if ($apps) {
                self::_log('getAllApps:get from cache!');
                return $apps;
            }
        }
        
        $ret = array();
        $db  = self::_getAppDb();
        if (!$db) {
            return $ret;
        }
        
        $sql = 'SELECT id, name, icon_small, icon_middle, icon_big '
             . 'FROM application WHERE `status`='.self::APP_STATUS_RUN;
        /*    
        if ($offset || $count) {
            $offset = intval($offset);
            $count = intval($count);
            $sql .= " LIMIT {$offset},{$count}";
        }*/
        $res = @mysql_query($sql, $db);
        if ($res) {
            $appImgPrefix = self::_getConfig('app_img_prefix');
            while(($row = @mysql_fetch_assoc($res)) != FALSE) {
                $ret[$row['id']] = array(
                    'name' => $row['name'],
                    'icon_small' => $appImgPrefix . $row['icon_small'],
                    'icon_middle' => $appImgPrefix . $row['icon_middle'],
                    'icon_big' => $appImgPrefix . $row['icon_big'],
                );
            }
            @mysql_free_result($res);
        }
        #@mysql_close($db);
        
        if (self::$useCache) {
            self::_setCache($cacheKey, $ret, self::MEMCACHE_SET_FLAGS, self::ALL_APPS_CACHE_TIME);
        }
        
        return $ret;
    }

    public static function clearAllAppsCache()
    {
        $cacheKey = self::_getAllAppCacheKey();

        self::_deleteCache($cacheKey);
    }   
     
    /**
     * 根据应用信息数组或应用id获取玩应用的页面链接
     *
     * @param array|integer $appInfoOrId 应用信息数据或应用id
     * @return string
     */ 
    public static function getAppPlayUrl($appInfoOrId)
    {
        $canvas_page_url = '';
        $appId = 0;
        if (is_array($appInfoOrId)) {
            $canvas_page_url = 
                isset($appInfoOrId['canvas_page_url']) ? $appInfoOrId['canvas_page_url'] : '';
            $appId = 
                isset($appInfoOrId['id']) ? $appInfoOrId['id'] : 0;
            $app = $appInfoOrId;
        } elseif (is_numeric($appInfoOrId)) {
            $app = self::getAppInfo($appInfoOrId);
            if ($app) {
                $canvas_page_url = $app['canvas_page_url'];
            }
            $appId = $appInfoOrId;
        }
        
        if ($app['app_type'] == self::APP_TYPE_SYSTEM) {
            return $app['canvas_callback_url'];    
        }
        
        $appBaseUrl = self::_getConfig('app_store_base_url');
        
        if (!empty($canvas_page_url)) {
            return "{$appBaseUrl}/{$canvas_page_url}";
        }
        if (!empty($appId)) {
            return "{$appBaseUrl}/play?id={$appId}";
        }
        return '';
    }
    
    /**
     * 用户添加应用接口
     *
     * @param integer $uid 
     * @param integer|array $appIds
     * @return boolean
     */
    public static function addUserApps($uid, $appIds)
    {
        if (!is_array($appIds)) {
            $appIds = array($appIds);
        }
        $db  = self::_getUserAppDb($uid, FALSE);
        if (!$db) {
            return FALSE;
        }

        $sql = 'SELECT max(`position`) FROM user_app WHERE uid='.$uid;
        $res = @mysql_query($sql, $db);
        if ($res === FALSE) {
            return FALSE;
        }
        $row = @mysql_fetch_array($res);
        if (empty($row)) {
            return FALSE;
        }
        $pos = intval($row[0]);
        @mysql_free_result($res);

        $sql = 'INSERT IGNORE user_app (uid, app_id, is_authorized, '
             . 'authorize_time, last_access_time, position) VALUES';
        $vals = array();
        foreach ($appIds as $appId) {
            $pos++;
            $vals[] = "($uid, $appId, 1, CURRENT_TIMESTAMP, '0000-00-00 00:00:00', $pos)";
        } 
        $sql .= implode(',', $vals);
        $ret = @mysql_query($sql, $db);
        #@mysql_close($db);
        if ($ret) {
            self::_clearUserAppsCache($uid);

            //添加应用用户记录
            foreach ($appIds as $appId) {
                $db = self::_getAppUserDb($appId, FALSE);
                if (!$db) {
                    //error
                    continue;
                }
                $sql = "INSERT IGNORE app_user (app_id, uid, last_access_time) VALUES ({$appId}, {$uid}, '0000-00-00 00:00:00')";
                if (!@mysql_query($sql, $db)) {
                    //error
                }
                #@mysql_close($db);
            }
        }
        return $ret;
    }
    
    /**
     * 删除用户应用
     *
     * @param integer $uid
     * @param integer|array $appIds
     * @return boolean
     */
    public static function deleteUserApps($uid, $appIds)
    {
        $uid = intval($uid);
        if (!is_array($appIds)) {
            $appIds = array($appIds);
        }
        $appIds = array_map('intval', $appIds);

        $db = self::_getUserAppDb($uid);
        if (!$db) {
            return FALSE;
        }

        $sql = "DELETE FROM user_app WHERE uid={$uid} AND app_id in (" 
             . implode(',', $appIds) . ')';
        $ret = @mysql_query($sql, $db);
        #@mysql_close($db);

        if ($ret) {
            self::_clearUserAppsCache($uid);
        }

        //删除应用－用户关系表记录
        foreach ($appIds as $appId) {
            $db = self::_getAppUserDb($appId);
            if (!$db) {
                self::_error('deleteUserApp: no app user db link,'.$appId);
                continue;
            }
            $sql = "DELETE FROM app_user WHERE app_id={$appId} AND uid={$uid}";
            @mysql_query($sql, $db);
            #@mysql_close($db);
            //统一监控 删除应用数统计
            #StatActionName('10|12|14|20');
            StatUserinfo('apps_user_remove', "{$uid}|{$appId}");
        }
        
        return $ret;
    }

    /**
     * 判断用户是否对一个应用授权过
     *
     * @param integer $uid
     * @param integer $appId
     * @return boolean
     */
    public static function isUserApp($uid, $appId)
    {
        $uid = intval($uid);
        $appId = intval($appId);
        $ret = TRUE;

        $db = self::_getUserAppDb($uid);
        if (!$db) {
            self::_error("isUserAuthorizedApp: no db link");
            return FALSE;
        }

        $sql = "SELECT COUNT(0) FROM user_app WHERE uid={$uid}"
             . " AND app_id={$appId} AND is_authorized=1";
        $res = @mysql_query($sql, $db);
        if ($res) {
            $row = @mysql_fetch_array($res);
            if ($row) {
                $ret = $row[0] > 0;
            }
            @mysql_free_result($res);
        }
        #@mysql_close($db);
        return $ret;
    }
    
    /**
     * 设置用户所有应用的顺序
     *
     * @param integer $uid
     * @param array $appIds 按位置排序的应用id
     * @return boolean
     */
    public static function setUserAppsPosition($uid, $appIds)
    {
        $uid = intval($uid);
        $appIds = array_map('intval', $appIds);

        $db = self::_getUserAppDb($uid, FALSE);
        if (!$db) {
            self::_error("setUserAppsPosition:no db link");
            return FALSE;
        }

        $i = 1;
        $ret = TRUE;
        foreach ($appIds as $appId) {
            $sql = "UPDATE user_app SET `position`={$i}"
                 . " WHERE uid={$uid} AND app_id={$appId} AND `position`!={$i}";
            if (@mysql_query($sql, $db) === FALSE) {
                self::_error("setUserAppsPosition:query error:".mysql_error());
                $ret = FALSE;
                break;
            }
            $i++;
        }
        
        if ($ret) {
            self::_clearUserAppsCache($uid);
        }
        
        return $ret;
    }

    /**
     * 交换用户两个应用的位置
     *
     * @param integer $uid 用户id
     * @param integer $sApp 应用信息，包含id,及原始位置
     * @param integer $dApp 应用信息，包含id,及原始位置
     * @return boolean
     */
    public static function exchangeUserApps($uid, $sApp, $dApp)
    {
        $uid = intval($uid);

        $sAppId = intval($sApp['id']);
        $sPos = intval($sApp['position']);

        $dAppId = intval($dApp['id']);
        $dPos = intval($dApp['position']);

        $db  = self::_getUserAppDb($uid, FALSE);
        if (!$db) {
            return FALSE;
        }

        $sql = "UPDATE user_app SET `position`={$dPos}"
             . " WHERE uid={$uid} AND app_id={$sAppId}";
        $ret = @mysql_query($sql, $db);
        if (!$ret) {
            #@mysql_close($db);
            return FALSE;
        }
        $sql = "UPDATE user_app SET `position`={$sPos}"
             . " WHERE uid={$uid} AND app_id={$dAppId}";
        $ret = @mysql_query($sql, $db);

        #@mysql_close($db);
        
        self::_clearUserAppsCache($uid);

        return $ret;
    }

    /**
     * 改变一个用户应用的位置
     *
     * @param integer $uid
     * @param integer $appId 
     * @param integer $afterPosition 应用的新位置的前一个应用的位置，如果应用放至了第1个，则该值为0
     * @return boolean
     */
    public static function changeUserAppPosition($uid, $appId, $afterPosition)
    {
        $uid = intval($uid);
        $appId = intval($appId);
        $afterPosition = intval($afterPosition);
        $toPosition = $afterPosition + 1;

        $db = self::_getUserAppDb($uid, FALSE);
        if (!$db) {
            self::_error("changeUserAppPosition:no db link");
            return FALSE;
        }
        
        $sql = "UPDATE user_app SET `position`=`position`+1"
            . " WHERE uid={$uid} AND `position`>={$toPosition}";
        $ret = @mysql_query($sql, $db);

        if (!$ret) {
            self::_error("changeUserAppPosition:query error\n$sql\n".mysql_error());
            return FALSE;
        }

        $sql = "UPDATE user_app SET `position`={$toPosition}"
             . " WHERE uid={$uid} AND app_id={$appId}";
        $ret = @mysql_query($sql, $db);

        if (!$ret) {
            self::_error("changeUserAppPosition:query error\n$sql\n".mysql_error());
            return FALSE;
        }
        
        self::_clearUserAppsCache($uid);

        return TRUE;
    }

    /**
     * 获取应用的邀请链接
     * 
     * @param integer|array $appInfoOrId 应用信息数组或ID
     * @param integer $inviterUid 邀请者uid
     * @param boolean $outer 是否是站外邀请
     * @return string 应用不存在或无效时，返回空字符串，否则返回邀请链接，
     *      站外邀请链接将返回的url做为回跳参数加在注册链接后
     */
    public static function getAppInviteUrl($appInfoOrId, $inviterUid, $outer = FALSE)
    {
        $baseUrl = self::getAppPlayUrl($appInfoOrId);
        
        $baseUrl .= (strpos($baseUrl, '?') !== FALSE ? '&' : '?') . 'from=' . $inviterUid;
        
        if ($outer) {
            $baseUrl .= '&new=1';
        }
        
        return $baseUrl;
    }
    
    public static function testMemcache()
    {
        $key = 'my_test_key';
        $val = 'hello,world!';
        $ret = self::_setCache($key, $val, 0, 60);
        if (!$ret) {
            echo "Set cache failed!\n";
            return FALSE;
        }
        $getVal = self::_getCache($key, 0);
        if ($getVal != $val) {
            echo "Get cache failed\n";
            return FALSE;
        }
        echo "cache ok\n";
        return TRUE;
    }
    
    public static function getLastError()
    {
        if (!empty(self::$_errors)) {
            return self::$_errors[count(self::$_errors) - 1];
        }
        return '';
    }

    public static function getErrors()
    {
        return self::$_errors;
    }

    public static function getLogs()
    {
        return self::$_logs;
    }
    //******************************
    // 公开接口 END
    //******************************
 
    protected static function _getApps($appIds)
    {
        if (empty($appIds)) {
            return array();
        }
        
        $returnAppArray = TRUE;
        
        if (!is_array($appIds)) {
            $appIds = array($appIds);
            //返回单个应用，而不是应用数组
            $returnAppArray = FALSE;
        }
        
        $appIds = array_unique($appIds);
        if (self::$useCache) {
            $appIdsUncached = NULL;
            $appsCached = &self::_getAppsFromCache($appIds, $appIdsUncached);
        } else {
            $appIdsUncached = &$appIds;
        }
        $appsUncached = NULL;

        if (!empty($appIdsUncached)) {
            
            $db  = self::_getAppDb();
            
            if ($db) {
                //Safe filter
                $appIdsUncached = array_map('intval', $appIdsUncached);
                
                $sql = 'SELECT * FROM application WHERE id IN (' 
                     . implode(',', $appIdsUncached) . ') AND `status`!=' 
                     . self::APP_STATUS_DELETE;
                
                $res = mysql_query($sql, $db);
                
                if ($res) {
                    
                    $appsUncached = array();
                    while (($row = mysql_fetch_assoc($res)) != FALSE) {
                        $appsUncached[] = $row;
                    }
                    @mysql_free_result($res);
                                        
                    if ($appsUncached) {
                        #echo "Get apps(".count($appsUncached).") From db\n";
                        self::_resetArrKey($appsUncached, 'id');
                        if (self::$useCache) {
                            self::_setAppsCache($appsUncached);
                        }
                    }
                } else {
                    self::_error("_getApps:query error!\nsql:{$sql}\n".mysql_error($db));
                }
                #@mysql_close($db);
            } else {
                self::_error("_getApps:no db link");
            }
        }
        if (self::$useCache) {
            self::_log("_getApps:" . ($appsUncached ? count($appsUncached) : 0)
                        . " app from db." . count($appsCached) . " app from cache."
                      );
            $apps = $appsCached + ($appsUncached ? $appsUncached : array());
        } else {
            $apps = &$appsUncached;
        }

        if ($apps) {
            $appImgPrefix = self::_getConfig('app_img_prefix');
            $imgNames = array('icon_small', 'icon_middle', 'icon_big');
            $appFiledsExportMap = array_flip(self::$_appFields);
            foreach ($apps as &$app) {
                //设置应用图片
                foreach ($imgNames as $imgName) {
                    if (!empty($app[$imgName])) {
                        $app[$imgName] = $appImgPrefix.$app[$imgName];
                    }
                }
                foreach (array_keys($app) as $field) {
                    if (!isset($appFiledsExportMap[$field])) {
                        unset($app[$field]);
                    }
                }
                $app['play_url'] = self::getAppPlayUrl($app);
                $app['target_blank'] = 
                    $app['canvas_type'] == self::CANVAS_TYPE_PAGE;
            }
        }

        return $returnAppArray ? $apps : (isset($apps[$appIds[0]]) ? $apps[$appIds[0]] : NULL);
    }
    
    /**
     * 设置缓存
     * 
     * @param array $apps
     * @return void
     */
    protected static function _setAppsCache(&$apps)
    {
        foreach ($apps as $appId => &$app) {
            $key = self::_getAppCacheKey($appId);
            $ret = self::_setCache(
                $key, $app, self::MEMCACHE_SET_FLAGS, 
                self::APP_CACHE_TIME, self::CACHE_CLUSTER_APPINFO
            );
            if ($ret) {
                self::_log("set app cache:$appId");
            }
            self::$_appCache[$appId] = $app;
        }
    }
    
    /**
     * 从缓存中获取应用信息数据，并过滤出缓存中不存在的app id
     * 
     * @param array $appIds app id array
     * @param array $appIdsUnCached out parameter
     * @return array
     */
    protected static function & _getAppsFromCache($appIds, &$appIdsUnCached = NULL)
    {
        $appsInPageCache = array();
        if (!empty(self::$_appCache)) {
            $appIdsUnCached = array();
            foreach ($appIds as $appId) {
                if (self::$_appCache[$appId]) {
                    $appsInPageCache[$appId] = self::$_appCache[$appId];
                } else {
                    $appIdsUnCached[] = $appId;
                }
            }
            if (empty($appIdsUnCached)) {
                return $appsInPageCache;
            }
            $appIds = $appIdsUnCached;
        }
        
        $keys = self::_getAppCacheKey($appIds);
        $apps = self::_getCache(
            $keys, self::MEMCACHE_SET_FLAGS, self::CACHE_CLUSTER_APPINFO
        );
        $appIdsUnCached = array();

        foreach ($keys as $key) {
            if (empty($apps[$key])) {
                $appIdsUnCached[] = self::_getAppIdFromCacheKey($key);
            }
        }
        
        self::_resetArrKey($apps, 'id');
        
        if ($appsInPageCache) {
            #echo "Get apps(".count($appsInPageCache).") From page cache\n";
            $apps = $appsInPageCache + $apps;
        }
        #echo "Get apps(".count($apps).") From cache\n";
        return $apps;
    }
    
    protected static function _clearUserAppsCache($uid)
    {
        $sort = self::getUserApps(0, 0, TRUE);
        $cacheKey = "user_{$uid}_apps_{$sort}";
        self::_deleteCache($cacheKey);
    
        $sort = self::getUserRecentlyPlayedApps(0, 0, TRUE);
        $cacheKey = "user_{$uid}_apps_{$sort}";
        self::_deleteCache($cacheKey);
    }

    //Interface for app store
    public static function clearUserAppsCache($uid)
    {
        self::_clearUserAppsCache($uid);
    }

    protected static function _getUserApps($uid, $sort, $count)
    {
        $cacheKey = "user_{$uid}_apps_{$sort}";
        
        $userApps = NULL;
        
        if (self::$useCache) {
            $userApps = self::_getCache($cacheKey, self::MEMCACHE_SET_FLAGS);
        }

        if (empty($userApps)) {
            $userApps = self::_getUserAppsFromDb($uid, $sort, 0);
            if (self::$useCache && $userApps) {
                self::_setCache(
                    $cacheKey, $userApps, 
                    self::MEMCACHE_SET_FLAGS, 
                    self::USER_APP_CACHE_TIME
                );
            }
        } else {
            self::_log("_getUserApps:app ids from cache");
        }

        if (!empty($userApps)) {
            $appIds = array_keys($userApps);
            $apps = self::getBatchAppInfo($appIds);
            $ret  = array();
            $retCount = 0;
            foreach ($appIds as $appId) {
                $app = isset($apps[$appId]) ? $apps[$appId] : NULL;
                if (!empty($app)) {
                    $userApp = $userApps[$appId];
                    $ret[$appId] = $app;
                    $ret[$appId]['position'] = $userApp['position'];
                    if ($count > 0 && ++$retCount == $count) {
                        break;
                    }
                } else {
                    //delete the record ?
                    self::_log("_getUserApps: app $appId not found");
                    self::deleteUserApps($uid, $appId);
                }
            }
            return $ret;
        }
        
        return array();
    }

    protected static function _getUserAppsFromDb($uid, $sort, $count)
    {
        $uid = intval($uid);
        $count = intval($count);

        $db = self::_getUserAppDb($uid);
        
        if ($db) {
            $sql = "SELECT * FROM user_app WHERE uid={$uid} AND is_authorized=1";
            //最近使用的应用不包含自动添加的应用还未玩过的应用
            if ($sort == self::getUserRecentlyPlayedApps(0, 0, TRUE)) {
                $sql .= " AND last_access_time!='0000-00-00 00:00:00'";
            }
            $sql .= " ORDER BY {$sort}";
            if ($count > 0) {
                $sql .= " LIMIT 0, {$count}";
            }
            $res = @mysql_query($sql, $db);
            $ret = array();
            if ($res) {
                while (($row = @mysql_fetch_assoc($res)) != FALSE) {
                    //$ret[] = $row['app_id'];
                    $ret[$row['app_id']] = $row;
                }
                @mysql_free_result($res);
                return $ret;
            }
            #@mysql_close($db);
        } else {
            self::_error("_getUserAppsFromDb:no db link");
        }
        
        return array();
    }
    
    /**
     * 获取应用的缓存名
     * 
     * @param integer $appId
     * @return string
     */
    protected static function _getAppCacheKey($appId)
    {
        if (is_array($appId)) {
            $ret = array();
            foreach ($appId as $val) {
                $ret[] = self::_getAppCacheKey($val);
            }
            return $ret;
        }
        return "app_$appId";
    }
    
    protected static function _getAllAppCacheKey()
    {
        return "all_apps";    
    }
    
    //Interface for app store
    public static function getAppCacheKey($appId)
    {
        return self::_getAppCacheKey($appId);
    }
    
    //Interface for app store
    public static function getUserFriendCountInAppCacheKey($uid, $appId)
    {
        return "user_{$uid}_app_{$appId}_friend_count";
    }
    
    public static function getUserFriendsInAppCacheKey($uid, $appId)
    {
        return "user_{$uid}_app_{$appId}_friends";    
    }
    
    //Inteface for app store
    public static function getUserAppAttrsCacheKey($uid, $appId)
    {
        return "user_{$uid}_app_{$appId}_attrs";    
    }
    
    /**
     * 根据应用缓存名获取app id
     * 
     * @param string $key
     * @return integer
     */
    protected static function _getAppIdFromCacheKey($key)
    {
        return intval(substr($key, 4));
    }

    //Interface for app store
    public static function getAppIdFromCacheKey($key)
    {
        return self::_getAppIdFromCacheKey($key);
    }

    protected static function _getAppDb($readDb = TRUE)
    {
        return self::_getSingleDb('app_store', $readDb);
    }
    
    protected static function _getDbInstance($dbConfig)
    {
        $key = $dbConfig['host']
             . '_'.$dbConfig['port']
             . '_'.$dbConfig['db_user'];
        
        if (isset(self::$_dbInstances[$key]) && 
            @mysql_ping(self::$_dbInstances[$key])
        ) {
            self::_log("Get db instance:{$key}");
            return self::$_dbInstances[$key]; 
        }
        
        $link = mysql_connect(
            "{$dbConfig['host']}:{$dbConfig['port']}", 
            $dbConfig['db_user'], 
            $dbConfig['db_pwd']
        );
        
        if ($link) {
            self::_log("Create db instance:{$key}");
            mysql_query("SET NAMES 'UTF8'", $link);
            self::$_dbInstances[$key] = $link;
            return $link;
        } else {
            self::_error("Connect {$dbConfig['host']}:{$dbConfig['port']} failed!");
        }
        return NULL;
    }
    
    /**
     * 获取单库db 
     * @param unknown_type $cluster
     * @param unknown_type $readDb
     */
    protected static function _getSingleDb($cluster, $readDb = TRUE) 
    {
        $config = self::_getConfig('db_singles', $cluster);
        $dbConfig = self::_getDbConfig($config['map'], $readDb);

        $dbName = $config['db_name'];
        
        $link = self::_getDbInstance($dbConfig);
        
        if ($link) {
            mysql_select_db($dbName, $link);
        }
        
        return $link;
    }
    
    /**
     * 获取分库db
     * @param unknown_type $cluster
     * @param unknown_type $objId
     * @param unknown_type $readDb
     */
    protected static function _getFarmDb($cluster, $objId, $readDb = TRUE)
    {
        $config = self::_getConfig('db_cluster', $cluster);
        $physical = self::_getConfig('db_physical');
        $hash_method = $config['farm_policy'];
        $farmId = call_user_func($hash_method, $objId);
        #$farmId = self::$hash_method($objId);
        if (isset($config['map'][$farmId])) {
            $farmMap = $config['map'][$farmId];
        } elseif (isset($config['map']['rule'])) {
            $farmMap = call_user_func($config['map']['rule'], $farmId);
        } else {
            $farmMap = $config['map'][0];
        }
        
        $dbConfig = self::_getDbConfig($farmMap, $readDb);
        
        $dbName = $config['db_name_prefix'].STR_PAD($farmId, 3, '0', STR_PAD_LEFT);
        
        $link = self::_getDbInstance($dbConfig);
        
        if ($link) {
            mysql_select_db($dbName, $link);
        }
        
        return $link;
    }
    
    protected static function _getDbConfig($map, $readDb = TRUE)
    {
        $config = array();
        $physical = self::_getConfig('db_physical');
        $mapPhysical = $physical[$map];
        
        $config['db_user'] = isset($mapPhysical['db_user']) ? 
            $mapPhysical['db_user'] : $physical['db_user'];
        $config['db_pwd'] = isset($mapPhysical['db_pwd']) ? 
            $mapPhysical['db_pwd'] : $physical['db_pwd'];
         
        if ($readDb) {
            if (isset($mapPhysical['read'])) {
                $mapPhysical = $mapPhysical['read'];
            }
        } else {
            if (isset($mapPhysical['write'])) {
                $mapPhysical = $mapPhysical['write'];
            }
        }
        
        $config['host']   = $mapPhysical['host'];
        $config['port']   = $mapPhysical['port'];
        
        return $config;
    }
    
    protected static function _getUserAppDb($uid, $readDb = TRUE)
    {
        return self::_getFarmDb('user_app', $uid);             
    }
    
    protected static function _getAppUserDb($appId, $readDb = TRUE)
    {
        return self::_getFarmDb('app_user', $appId, $readDb);
    }

    protected static function _deleteCache($key, $cluster = self::CACHE_CLUSTER_SHARE)
    {
        $memcache = self::_getMemcache($cluster);
        if (!$memcache) {
            return FALSE;
        }

        return @$memcache->delete(md5($key), 0);
    }

    protected static function _getCache($key, $flag, $cluster = self::CACHE_CLUSTER_SHARE)
    {
        $memcache = self::_getMemcache($cluster);
        if (!$memcache) {
            return FALSE;
        }

        if (is_array($key)) {
            $keyMap = array();
            $max = count($key);
            for ($i = 0; $i < $max; $i++) {
                $md5Key = md5($key[$i]);
                $keyMap[$md5Key] = $key[$i];
                $key[$i] = $md5Key;
            }
        } else {
            $key = md5($key);
        }

        $res = @$memcache->get($key, $flag);
        
        if (is_array($key)) {
            $nwRes = array();
            if ($res) {
                foreach ($res as $md5Key => &$val) {
                    $nwRes[$keyMap[$md5Key]] = &$val;
                }    
            }
            return $nwRes;
        }

        return $res;       
    }
    
    protected static function _setCache($key, $val, $flag, $expires, $cluster = self::CACHE_CLUSTER_SHARE)
    {
        $memcache = self::_getMemcache($cluster);
        if (!$memcache) {
            self::_error("_setCache:no memcache");
            return FALSE;
        }
        $ret = $memcache->set(md5($key), $val, $flag, $expires);
        if (!$ret) {
            self::_error("_setCache: failed. key:$key, $val, $flag, $expires");
        }
        return $ret;
    }
       
    /**
     * Get memcache object
     * @param string $cluster share|appinfo defined in config
     */
    protected static function _getMemcache($cluster = self::CACHE_CLUSTER_SHARE)
    {
        if (!isset(self::$_memcaches[$cluster])) {
            $cachePhysical = self::_getConfig('cache_physical');
            $maps = self::_getConfig('cache_cluster', $cluster);
            
            $memcache = new Memcache;
            if ($cluster == self::CACHE_CLUSTER_APPINFO) {
                //For app info, Choose a memcache instance randomly
                $map = $maps[array_rand($maps)];
                $server = $cachePhysical[$map];
                self::_log("User memcache instance({$server['host']}:{$server['port']}) for app info");
                $maps = array($map);
            }
            foreach ($maps as $map) {
                $server = $cachePhysical[$map];
                $memcache->addserver($server['host'], $server['port']);
            }
            self::$_memcaches[$cluster] = $memcache;
        }
        return self::$_memcaches[$cluster];
    }
    
    /**
     * 将二维数组第二维的一列作为键名
     * 
     * @param array $arr in/out parameter
     * @param string $column
     */
    protected static function _resetArrKey(&$arr, $column)
    {
        if (empty($arr)) {
	        return;
	    }
	    $newArr = array();
	
	    foreach ($arr as $val) {
	        $newArr[$val[$column]] = $val;
	    }
	    $arr = $newArr;
    }
    
    protected static function _error($err)
    {
        self::$_errors[] = $err;
    }

    protected static function _log($log)
    {
        if (self::$enableLog) {
            self::$_logs[] = $log;
        }
    }

    protected static function _getConfig($key, $subKey = NULL)
    {
        $val = $GLOBALS['g_sdo_app_store_config'][$key];

        if ($subKey !== NULL && $val) {
            return $val[$subKey];
        }

        return $val;
    }
}
